AWS Lambdaでカスタマー管理キーによる環境変数のKMS暗号化を無効にしたらKMSAccessDeniedExceptionが発生したので原因調査した話
こんにちは、CX事業本部の若槻です。
AWS Lambdaで関数に設定した環境変数が保管される際の暗号化には、既定ではAWSが管理するキーが使われますが、ユーザーがAWS KMSで作成した独自のキー(カスタマー管理キー)を使用することも可能です。
今回は、今までこのカスタマー管理キーによる環境変数の暗号化を行っていたLambdaを、AWS管理のキーを使用するように設定を戻したところ、Lambda実行時にKMSAccessDeniedException
が発生するようになり原因調査を行ったのでご紹介します。
構成の変更内容
まず、エラー発生の前後でシステムをどのように構成変更をしていたのかを、CloudFormationテンプレートとLambdaコードのパッチファイルで以下に示します。
CloudFormationテンプレート(の一部)変更内容
- Lambdaの環境変数をデプロイ時にSSMパラメータから取得するようにしたため、パラメータ
accessToken
を追加 - カスタマー管理キーの使用をやめてAWS KMSキーが不要となったため、リソース
Key00
を削除 - Lambdaの環境変数の暗号化でカスタマー管理キーの使用をやめたため、リソース
Function00
でEnvironment - Variables - ACCESS_TOKEN
の値をRef! accessToken
に変更
Parameters: appName: Type: String bucketName: Type: String + accessToken: + Type: AWS::SSM::Parameter::Value<String> + Default: accessToken + Resources: Role00: Type: AWS::IAM::Role Properties: RoleName: !Sub ${appName}-aws-role-00 AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Policy00: Type: AWS::IAM::Policy Properties: PolicyName: !Sub ${appName}-aws-policy-00 PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: "*" Roles: - !Ref Role00 - Key00: - Type: AWS::KMS::Key - Properties: - KeyPolicy: - Version: "2012-10-17" - Id: !Sub ${appName}-aws-apikey-00-keypolicy - Statement: - - - Sid: "Allow administration of the key" - Effect: "Allow" - Principal: - AWS: !Sub arn:aws:iam::${AWS::AccountId}:root - Action: "*" - Resource: "*" - - - Sid: "Allow use of the key" - Effect: "Allow" - Principal: - AWS: !GetAtt Role00.Arn - Action: - - "kms:Encrypt" - - "kms:Decrypt" - - "kms:ReEncrypt*" - - "kms:GenerateDataKey*" - - "kms:DescribeKey" - Resource: "*" Function00: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${appName}-aws-function-00 Code: S3Bucket: !Ref bucketName S3Key: function.zip Handler: lambda_function.lambda_handler MemorySize: 512 Timeout: 60 Role: !GetAtt Role00.Arn Runtime: python3.7 Environment: Variables: - ACCESS_TOKEN: DummyAccessToken + ACCESS_TOKEN: Ref! accessToken KmsKeyArn: !GetAtt Key00.Arn
リソースFunction00
で既存のEnvironment - Variables - ACCESS_TOKEN
の値をDummyAccessToken
としていた理由は、デプロイ後に環境変数の実際の値の設定と、伝送中の暗号化のためのヘルパーの有効化
をコンソールから手動で行っていたためです。
なお、伝送中の暗号化のためのヘルパーの有効化
は、以前に次の記事で書いた通り、CloudFormationによる設定ができなかったため仕方なく手動で設定をしたという経緯があります。
Lambdaコード(の一部)変更内容
- 環境変数の値の取得後のKMS復号化をしないように変更
import os import boto3 from base64 import b64decode - access_token = boto3.client('kms').decrypt(CiphertextBlob=b64decode(os.environ['ACCESS_TOKEN']))['Plaintext'].decode() + access_token = os.environ['ACCESS_TOKEN']
変更後に発生した事象
前述の構成変更を行った上で同じLambdaを呼び出したところ次のKMSAccessDeniedException
エラーが発生するようになりました。
[ERROR] KMSAccessDeniedException: An error occurred (KMSAccessDeniedException) when calling the Invoke operation (reached max retries: 4): Lambda was unable to decrypt the environment variables because KMS access was denied. Please check the function's KMS key settings. KMS Exception: UnrecognizedClientExceptionKMS Message: The security token included in the request is invalid.
このエラーは、次のドキュメントによるとキーが存在しないかキーへのアクセス権がない場合に発生するとのことです。
The ciphertext references a key that doesn't exist or that you don't have access to.
しかし、CloudFormationスタックではIAMロールからKMSキーへの参照は削除し、LambdaからもKMS暗号化の設定を削除していたため、KMSキーが使われるはずがなく、なぜこのエラーが発生するのか分かりませんでした。
調査
Serverless FrameworkのGitHub Repositoryで次のIssueが上がっているのを見つけました。
Serverless FrameworkでデプロイしたLambdaで今回と同様のエラーが発生しているという内容です。そしてIssueのスレッドの中でやけにリアクションを集めている次の投稿が目に留まりました。
I had this issue and after some head banging found out it was due to deleting an IAM policy and creating using the same name, simply changing the IAM of the lambda to something else, saving and then changing back fixed it.
事象の原因はIAMポリシーを削除して同じ名前で作成していたことで、IAMを別の名前に変更したら解消したとのことです。
思い返せば、わたしはシステムの変更前に、一度CloudFormationスタックの削除の操作を行った(理由は忘れました)後に、スタックテンプレートおよびLambdaコードを変更してcloudformation create-stack
をしたら、そのLambdaで今回の事象が発生するようになったので、条件は酷似しています。
そこで私も倣ってスタックテンプレートでIAMロールの物理名を次のように変更してスタックcloudformation update-stack
したところ、Lambdaはエラーなく実行できるようになりました。
Resources: Role00: Type: AWS::IAM::Role Properties: - RoleName: !Sub ${appName}-aws-role-00 + RoleName: !Sub ${appName}-aws-role-01 AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole'
結論(原因の推測とワークアラウンド)
さらに、ロール名変更により一旦は事象が解消した後に、同Issue内での次の投稿にも目が留まりました。
I believe this is because the lambda references the identifier of the IAM role to use, not the ARN of the IAM role. Read more about identifiers here : https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids
Is there any update on this? This happens to us fairly consistently when doing a remove followed shortly by a re-deploy. The issue usually resolves itself within 5-10 minutes. Is there anything we can add to our deployment to speed that up?
意訳すると次のようになります。
- AWSドキュメントによると、IAMを同じ名称で再作成したとしても、IAMは
AIDAJQABLZS4A3QDU576Q
のような一意の識別子により管理されるため、以前のアクセス権などの情報が残ることはない - しかし、今回のエラーはIAM再作成から5~10分程度で発生した後に自然に解消する動作となった
この投稿がヒントとなり、私は今回の事象は次の仕様およびオペレーションが原因となり発生したと推測しました。
- 同じ名称で再作成したIAMロールは、AWSシステム内でIAMの識別子が変更されるまでにタイムラグがある。
- よって、再作成直後にLambdaがそのIAMロールを使用したため、以前の識別子のIAMとして削除済みのKMSキーを参照した。
- しかし、参照されたKMSキーは削除済みであったためLambdaの実行が
KMSAccessDeniedException
となった。
よって、今回の事象が発生した際は次のようなワークアラウンドにより解消できると考えられます。
- IAMロールの名称(物理名)を変更する
- IAMロールの識別子がAWSシステム内で切り替わるまで待つ
以上が今回の調査により私が結論づけた原因(推測)とワークアラウンドとなります。
おわりに
AWS Lambdaでカスタマー管理キーによる環境変数のKMS暗号化を無効にしたらKMSAccessDeniedExceptionが発生するようになり原因調査を行ったのでご紹介しました。
原因についてはあくまで他のユーザーや今回の私の動作状況を元にした推測ではあるのですが、同じ事象が発生した方は今回ご紹介したワークアラウンドを実施してみると解消するかもしれません。
参考
- AWS Key Management Service とは | AWS Key Management Service 開発者ガイド
- AWS::KMS::Key | AWS CloudFormation ユーザーガイド
以上